﻿#region [ License information          ]
/* ************************************************************
 *
 * Copyright (c) Bjørn Bouet Smith, 2010, 2011
 *
 * This source code is subject to terms and conditions of 
 * Microsoft Public License (Ms-PL).
 * 
 * A copy of the license can be found in the license.txt
 * file at the root of this distribution. If you can not 
 * locate the License, please send an email to bjornsmith@gmail.com
 * 
 * By using this source code in any fashion, you are 
 * agreeing to be bound by the terms of the Microsoft 
 * Permissive License.
 *
 * You must not remove this notice, or any other, from this
 * software.
 *
 * ************************************************************/
#endregion

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Smith.AMC.Operations;

namespace Smith.AMC.Serialization
{
    /// <summary>
    /// Class that have the responsibility to serialize and deserialize objects to and from Operations.
    /// </summary>
    public class ItemSerializer
    {
        /// <summary>
        /// Flag to indicate that it was not an object that was serialized but a byte stream.
        /// </summary>
        private const ushort RawFlag = 0xffff;

        /// <summary>
        /// Flag to indicate an empty byte array.
        /// </summary>
        private static readonly byte[] EmptyArray = new byte[0];

        /// <summary>
        /// Serializes the specified object of the given type to the specified Operation
        /// </summary>
        /// <param name="type">The type of object to serialize.</param>
        /// <param name="objectToSerialize">The object to serialize.</param>
        /// <param name="serializeTo">The operation where the object should be serialized to.</param>
        public void Serialize(Type type, object objectToSerialize, Operation serializeTo)
        {
            if (serializeTo == null)
            {
                throw new ArgumentNullException("serializeTo");
            }

            // null is passed, just return quickly
            if (objectToSerialize == null)
            {
                serializeTo.Data = EmptyArray;
                serializeTo.Flags = RawFlag;
                return;
            }

            byte[] tmpByteArray = objectToSerialize as byte[];

            TypeCode typeCode = Type.GetTypeCode(type);

            serializeTo.Flags = (ushort)typeCode;

            // Raw byte array passed, return that as well
            if (tmpByteArray != null)
            {
                serializeTo.Data = tmpByteArray;
                serializeTo.Flags = RawFlag;
                return;
            }

            switch (typeCode)
            {
                case TypeCode.Empty:
                    serializeTo.Data = EmptyArray;
                    break;

                case TypeCode.String:
                    serializeTo.Data = Encoding.UTF8.GetBytes((string)objectToSerialize);
                    break;

                case TypeCode.Boolean:
                    serializeTo.Data = BitConverter.GetBytes((bool)objectToSerialize);
                    break;

                case TypeCode.Int16:
                    serializeTo.Data = BitConverter.GetBytes((short)objectToSerialize);
                    break;

                case TypeCode.Int32:
                    serializeTo.Data = BitConverter.GetBytes((int)objectToSerialize);
                    break;

                case TypeCode.Int64:
                    serializeTo.Data = BitConverter.GetBytes((long)objectToSerialize);
                    break;

                case TypeCode.UInt16:
                    serializeTo.Data = BitConverter.GetBytes((ushort)objectToSerialize);
                    break;

                case TypeCode.UInt32:
                    serializeTo.Data = BitConverter.GetBytes((uint)objectToSerialize);
                    break;

                case TypeCode.UInt64:
                    serializeTo.Data = BitConverter.GetBytes((ulong)objectToSerialize);
                    break;

                case TypeCode.Char:
                    serializeTo.Data = BitConverter.GetBytes((char)objectToSerialize);
                    break;

                case TypeCode.DateTime:
                    serializeTo.Data = BitConverter.GetBytes(((DateTime)objectToSerialize).ToBinary());
                    break;

                case TypeCode.Double:
                    serializeTo.Data = BitConverter.GetBytes((double)objectToSerialize);
                    break;

                case TypeCode.Single:
                    serializeTo.Data = BitConverter.GetBytes((float)objectToSerialize);
                    break;

                default:
                    using (MemoryStream ms = new MemoryStream())
                    {
                        new BinaryFormatter().Serialize(ms, objectToSerialize);

                        serializeTo.Flags = (ushort)TypeCode.Object;
                        serializeTo.Data = ms.ToArray();
                    }

                    break;
            }
        }

        /// <summary>
        /// Deserializes the byte array into an object
        /// </summary>
        /// <param name="flag">The flag that determines what type of data that should be deserialized.</param>
        /// <param name="data">The actual data to be deserialized.</param>
        /// <param name="index">The index within the byte array to begin the deserialization.</param>
        /// <param name="length">The length of the data to deserialize.</param>
        /// <returns>The object that was deserialized.</returns>
        public object Deserialize(ushort flag, byte[] data, int index, int length)
        {
            if (flag == RawFlag)
            {
                return data;
            }

            TypeCode typeCode = (TypeCode)flag;

            if (typeCode == TypeCode.Empty)
            {
                return null;
            }

            switch (typeCode)
            {
                case TypeCode.String:
                    return Encoding.UTF8.GetString(data, index, length);

                case TypeCode.Boolean:
                    return BitConverter.ToBoolean(data, index);

                case TypeCode.Int16:
                    return BitConverter.ToInt16(data, index);

                case TypeCode.Int32:
                    return BitConverter.ToInt32(data, index);

                case TypeCode.Int64:
                    return BitConverter.ToInt64(data, index);

                case TypeCode.UInt16:
                    return BitConverter.ToUInt16(data, index);

                case TypeCode.UInt32:
                    return BitConverter.ToUInt32(data, index);

                case TypeCode.UInt64:
                    return BitConverter.ToUInt64(data, index);

                case TypeCode.Char:
                    return BitConverter.ToChar(data, index);

                case TypeCode.DateTime:
                    return DateTime.FromBinary(BitConverter.ToInt64(data, index));

                case TypeCode.Double:
                    return BitConverter.ToDouble(data, index);

                case TypeCode.Single:
                    return BitConverter.ToSingle(data, index);

                case TypeCode.Object:
                    using (MemoryStream ms = new MemoryStream(data, index, length))
                    {
                        return new BinaryFormatter().Deserialize(ms);
                    }

                default:
                    throw new InvalidOperationException(string.Format("Invalid TypeCode was returned: {0}", typeCode));
            }
        }
    }
}
